#pragma once

#include<iostream>
#include<vector>
#include<fstream>
#include"log.hpp"
#include<string>
#include<unordered_map>
#include"util.hpp"
#include<mutex>
#include<boost/algorithm/string/case_conv.hpp>



//正排索引：根据id查找文档内容
//倒排索引：根据关键字查找文档的id

namespace ns_index
{

    //文档的内容和其对应的id
    struct DocInfo
    {
        std::string title;//文档的标题
        std::string content;//去标签之后的内容
        std::string url;//官方的url
        uint64_t doc_id;//文档的id
    };

    //倒排索引的元素
    struct InvertedElem
    {
        std::string word;
        uint64_t id;
        int weight;//相关性
    };

    //存放我们title和content关键字出现的次数，用于词频统计
    typedef struct word_cnt
    {
        word_cnt():title_cnt(0),content_cnt(0)
        {}
        int title_cnt;
        int content_cnt;
    }word_cnt_t;

    //由于一个关键字对应一到多个文档id，因此我们用vector存放倒排元素，方便存储多个元素(倒排拉链)。
    typedef std::vector<InvertedElem> inverted_list;

    class Index
    {
    private:
        Index()
        {}
        Index(const Index&) = delete;
        Index& operator=(const Index&) = delete;

        static Index* index;
        static std::mutex mutex_;
    public:
        
        ~Index()
        {}

        static Index* GetInstance()
        {
            if(nullptr == index)
            {
                mutex_.lock();
                if(nullptr == index)
                {
                    index = new Index();
                }
                mutex_.unlock();
            }
            return index;
        }

        //正排索引，只要id合法，返回id再vector下标对应的文档地址即可
        DocInfo* GetForwardIndex(uint64_t doc_id)
        {
            if(doc_id >= forward_index.size())
            {
                std::cerr << "doc_id is out range, error" << std::endl;
                return nullptr;
            }
            return &forward_index[doc_id];
        }

        //倒排索引，查找是否有此关键字，有返回value的地址即可
        inverted_list* GetInvertedList(const std::string& word)
        {
            auto iter = inverted_inedx.find(word);
            if(iter == inverted_inedx.end())
            {
                std::cerr << word << " have no invertedlist" << std::endl;
                return nullptr;
            }
            return &iter->second;
        }

        //根据存放解析之后文档的文件，来构建我们的索引
        bool BuildIndex(const std::string& parse_path)
        {
            //要构建索引，先提取我们的文档信息
            std::ifstream in(parse_path,std::ios::in | std::ios::binary);
            if(!in.is_open())
            {
                std::cerr << "open " << parse_path << "error" << std::endl;
                return false;
            }
            std::string line;
            //利用string的getline方法读取我们的每一个文档内容，因为我们之前在每个文档之前放了\n换行符，因此读取很方便
            //需要注意getline会自动将\n换行符读走，因此不用自己处理
            int count = 0;
            while(std::getline(in,line))
            {
                //对获取到的每个文档做正排、倒排索引的构建
                DocInfo* pdoc = BuildForwardIndex(line);
                if(pdoc == nullptr)
                {
                    std::cerr << "nuild " << line << "error!" << std::endl;
                    continue;
                }
                //我们只需要接收正排索引中存放的文档指针即可
                //因为倒排索引需要我们的文档，因此传入我们新构建的文档
                BuildInvertedIndex(*pdoc);
                if(count % 50 == 0)
                {
                    LOG(INFO,"已经构建索引的文档: " + std::to_string(count));
                    //std::cout <<"已经构建索引的文档: " << count << std::endl;
                }
                count++;
            }


            return true;
        }

    private:
        DocInfo* BuildForwardIndex(const std::string& line)
        {
            const std::string sep = "\3";
            std::vector<std::string> result;
            //利用util工具中的boost字符切割方法
            ns_util::StringUtil::StringSplit(line,&result,sep);
            DocInfo doc;
            //填写文档对应的信息
            doc.title = result[0];
            doc.content = result[1];
            doc.url = result[2];
            //文档id就是没插入我们正排索引之前的size大小
            //我们填写再插入之前即可
            doc.doc_id = forward_index.size();
            //插入文档数据,为了效率，用右值引用，将局部doc拥有权转移，少了一次拷贝
            forward_index.push_back(std::move(doc));
            
            //返回本次文档的指针
            return &forward_index.back();
        }

//创建title和content的权重值
#define X 10
#define Y 1

        bool BuildInvertedIndex(const DocInfo& doc)
        {
            //1.单词划分
            //存放我们的title的关键词
            std::vector<std::string> title_words; 
            //存放我们的content的关键词
            std::vector<std::string> content_words; 
            //利用我们的jieba分词工具对title和content进行分词
            ns_util::JieBaUtil::CutString(doc.title,&title_words);

            //for debug:查看jieba分词的效果
            // if(doc.doc_id == 7330)
            // for(auto & word : title_words)
            //     std::cout << "title: " << word << std::endl;

            ns_util::JieBaUtil::CutString(doc.content,&content_words);

            //for debug:查看jieba分词的效果
            // if(doc.doc_id == 7330)
            // for(auto & word : content_words)
            //     std::cout << "content: "<< word << std::endl;

            //2.词频统计
            //创建hash对我们的关键词进行分类次数的统计（词频统计）
            std::unordered_map<std::string,word_cnt_t> word_cnt;
            //title
            for(auto word : title_words)
            {
                //关键字是不分大小写的
                boost::to_lower(word);
                word_cnt[word].title_cnt++;
            }
            
            //content
            for(auto word : content_words)
            {
                //关键字是不分大小写的
                boost::to_lower(word);
                word_cnt[word].content_cnt++;
            }


            //3.便利hash构建倒排索引
            for(auto& [word,word_cnt_t] : word_cnt)
            {
                //创建倒排拉链的元素
                InvertedElem elem;
                elem.id = doc.doc_id;
                elem.word = word;
                elem.weight = X * word_cnt_t.title_cnt + Y * word_cnt_t.content_cnt;//相关性
                //存放在倒排拉链中——这样在不同文档中同一个关键字就可以映射到不同的doc_id了
                inverted_inedx[word].push_back(std::move(elem));
            }

            return true;
        }

        //使用数组的原因是因为方便我们查找，id就对应我们的下标。
        std::vector<DocInfo> forward_index;

        //关键字和倒排拉链的映射关系，一个关键字可能对应多个InvertedElem
        std::unordered_map<std::string,inverted_list> inverted_inedx;
    };

    Index* Index::index = nullptr;
    std::mutex Index::mutex_;
}